/*
 -- IOTA Crypto Core
 --
 -- 2018 by Thomas Pototschnig <microengineer18@gmail.com>
 -- discord: pmaxuw#8292
 -- https://gitlab.com/iccfpga-rv
 --
 -- Permission is hereby granted, free of charge, to any person obtaining
 -- a copy of this software and associated documentation files (the
 -- "Software"), to deal in the Software without restriction, including
 -- without limitation the rights to use, copy, modify, merge, publish,
 -- distribute, sublicense, and/or sell copies of the Software, and to
 -- permit persons to whom the Software is furnished to do so, subject to
 -- the following conditions:
 --
 -- The above copyright notice and this permission notice shall be
 -- included in all copies or substantial portions of the Software.
 --
 -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 -- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 -- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 -- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 -- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 -- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 -- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWAR
 */

#include <unistd.h>
#include <ArduinoJson.h>

// Xilinx specific headers
#include "xparameters.h"
#include "xgpio.h"
#include "xdebug.h"

#include "micro-os-plus/diag/trace.h"
#include "debugprintf.h"

#include "secure/secure.h"
#include "secure/sbi.h"

#include "api/API.h"

#include "fpga/test.h"

#include "iota/transaction.h"
#include "iota/transfers.h"
#include "iota/bundle.h"

#include "utils.h"


extern uint32_t systick;

// buffer for string <-> json object
char charBuffer[32768];
StaticJsonDocument<32768> jsonDocument;


Transaction txs[MAX_NUM_TRANSACTIONS];
buffer_t buffer;



API::apiFlagsStruct apiFlags;

namespace API {
	JsonObject json;

	const int MAX_NUM_APICALLS = 30;

	namespace {
		apiCallsStruct apiCalls[MAX_NUM_APICALLS];
	}
	int numAPICalls = 0;

	bool registerAPICall(const char* command, apiCallType fPtr) {
		if (numAPICalls >= MAX_NUM_APICALLS)
			return false;
		apiCalls[numAPICalls].command = command;
		apiCalls[numAPICalls].fPtr = fPtr;
		numAPICalls++;
		return true;
	}

	int apiError(const char* error, bool output) {
		json.clear();
		json["code"] = int(API_ERROR);
		json["error"] = error;


		if (output) {
			serializeJson(json, charBuffer, sizeof(charBuffer)-1);
			write(XPAR_AXI_UARTLITE_0_DEVICE_ID, charBuffer, strlen(charBuffer));
			write(XPAR_AXI_UARTLITE_0_DEVICE_ID, "\n", 1);
		}
		debugPrintf("%s\n", error);
		return API_ERROR;
	}

	int apiError(SBIError error) {
		return apiError(SBIResponse::errorMsg(error));
	}

	int apiSetFlags() {
		if (
			!exists<JsonObject>(json, "flags")
		) {
			return apiError(SBIError::MISSING_OR_INVALID);
		}

		if (!json.containsKey("flags")) {
			return apiError("flags keyword not found");
		}

		JsonObject flags = json["flags"];
	/*
		if (flags.containsKey("keepSeedInRAM")) {
			bool keepSeedInRAM = flags["keepSeedInRAM"];
			for (int i = 0; i < 8; i++) {
				seeds[i].setCacheSeed(keepSeedInRAM);
			}
		}
	*/
	#ifdef DEBUG
		// no debugging on UART in Release mode
		if (flags.containsKey("debugRS232Output")) {
			bool flag = flags["debugRS232Output"];
			apiFlags.debugRS232Output = flag;
		}
		// Semihosting only in DEBUG available
		// if used in release mode, it will stall the cpu forever
		if (flags.containsKey("debugSemihostingOutput")) {
			bool flag = flags["debugSemihostingOutput"];
			apiFlags.debugSemihostingOutput = flag;
		}
	#endif
		json.clear();
		return API_SUCCESS;
	}

	int apiVersion() {
		json.clear();
		json["version"] = VERSION;
		return API_SUCCESS;
	}

	int apiGetLimits() {
		json.clear();
		json["maxTransactions"] = MAX_NUM_TRANSACTIONS;
		json["maxAddresses"] = MAX_NUM_ADDRESSES;
		return API_SUCCESS;
	}

	int apiReboot() {
		if (
			!exists<uint32_t>(json, "address")
		) {
			return apiError(SBIError::MISSING_OR_INVALID);
		}

		int address = json["address"];
		if ((address & 0x0000ffff) || address > 0x1000000) {
			return apiError(SBIError::INVALID_ADDRESS);
		}

		SBIResponse* resp = SBI::reboot((uint32_t) address);
		if (resp->isError())
			return apiError(resp->code);

		json.clear();
		return API_SUCCESS;
	}

	int apiTestHardwareAcceleration() {
		(void) json;
		json.clear();

#ifdef ENABLE_FPGA_TESTS
		TestAccelerationResponse* resp = SBI::testHardwareAcceleration();

		json["pow"] = resp->pow ? "pass" : "fail";
		json["curl"] = resp->curl ? "pass" : "fail";
		json["keccak384"] = resp->keccak384 ? "pass" : "fail";
		json["troika"] = resp->troika ? "pass" : "fail";
		json["sha512"] = resp->sha512 ? "pass" : "fail";
		json["bigintToTritsRandomRepeated"] = resp->bigintToTritsRandomRepeated ? "pass" : "fail";
		json["tritsToBigintRandomRepeated"] = resp->tritsToBigintRandomRepeated ? "pass" : "fail";
		json["trytesToBigintRandomRepeated"] = resp->trytesToBigintRandomRepeated ? "pass" : "fail";
		json["bigintToTrytesRandomRepeated"] = resp->bigintToTrytesRandomRepeated ? "pass" : "fail";
		return API_SUCCESS;
#else
		return apiError("testing not enabled in firmware");
#endif
	}

	bool registerAPIBase() {
		return
				registerAPICall("getLimits", &apiGetLimits) &&
				registerAPICall("setFlags", &apiSetFlags) &&
				registerAPICall("testHardwareAcceleration", &apiTestHardwareAcceleration) &&
				registerAPICall("reboot", &apiReboot) &&
				registerAPICall("version", &apiVersion);

	}

	void runAPI() {
		// it's enough to do it once
		json = jsonDocument.to<JsonObject>();

		while (1) {
			memset(charBuffer, 0, sizeof(charBuffer));	// remove it later if it works
			json.clear();

			int r = read(XPAR_AXI_UARTLITE_0_DEVICE_ID, (void*) charBuffer, sizeof(charBuffer) - 1);
			if (r >= (int) sizeof(charBuffer) - 1) {
				API::apiError("received too many bytes", true);
				continue;
			}
			charBuffer[r] = 0;	// zero-terminator; read returns if \n or \r was read

			DeserializationError error = deserializeJson(jsonDocument, charBuffer);
			if (error) {
				API::apiError("json decoding unsuccessful", true);
				continue;
			}
			if (!exists<const char*>(json, "command")) {
				API::apiError("command missing", true);
				continue;
			}

			const char* command = json["command"];
			if (!validateString(command)) {
				API::apiError("invalid command", true);
				continue;
			}

			char cmdbuf[64] = {0};
			if (strlen(command) > sizeof(cmdbuf)-1) {
				API::apiError("invalid command", true);
				continue;
			}

			strncpy(cmdbuf, command, sizeof(cmdbuf));


			apiCallType fPtr = 0;
			int idx = 0;
			do {
				// find command
				if (!memcmp(command, apiCalls[idx].command, strlen(apiCalls[idx].command))) {
					fPtr = apiCalls[idx].fPtr;
					break;
				}
				idx++;
			} while (idx < numAPICalls /*apiCalls[idx].fPtr*/);

			if (fPtr) {

// MCYCLE is a high precision counter but it has (some) overhead because
// CSRs are only accessible in privileged mode
//				MCycleResponse* mcycle = SBI::getMCycles();
//				uint64_t tickStart = mcycle->ticks;

				uint32_t timestamp = systick;
				int code = (*apiCalls[idx].fPtr)();
				json["command"] = cmdbuf;

//				mcycle = SBI::getMCycles();
//				uint32_t duration = uint32_t((mcycle->ticks - tickStart) / 100000ull);

				if (code == API_ERROR) {
					// error ...
				} else {
					json["duration"] = systick - timestamp;
//					json["duration"] = duration;
					json["code"] = int(code);
				}

				uint32_t retSize = serializeJson(json, charBuffer, sizeof(charBuffer)-1);
				if (retSize >= sizeof(charBuffer) - 1) {
					break;	// return error
				}
				write(XPAR_AXI_UARTLITE_0_DEVICE_ID, charBuffer, strlen(charBuffer));
				write(XPAR_AXI_UARTLITE_0_DEVICE_ID, "\n", 1);
	//			printf("%s\r\n\n\n", charBuffer);	// print to UART
			} else {
				API::apiError("unknown command", true);
			}
		}
	}
}
